package controllers

import (
	"beego/models"
	"crypto/md5"
	"fmt"
	"github.com/astaxie/beego"
	"github.com/astaxie/beego/logs"
	"github.com/astaxie/beego/orm"
	"math"
	"math/rand"
	"os"
	"path"
	"strings"
	"time"
)

type ArticleController struct {
	beego.Controller
}

// 查询文章列表
func (this *ArticleController) Index() {
	tagName := this.GetString("select")
	o := orm.NewOrm()
	var articles []models.Article
	// _, err := o.QueryTable("Article").All(&articles)
	// if err != nil {
	//	logs.Info("查询失败", err)
	//	return
	// }
	// count, _ := o.QueryTable("Article").Count()
	qs := o.QueryTable("Article")
	// 根据 tagName 判断查询条件
	if tagName != "" {
		qs = qs.Filter("Tags__Tag__Name", tagName)
	}
	// 一对多查询，关联用户信息
	// count, _ := o.QueryTable("Article").RelatedSel("User").Count()
	count, _ := qs.RelatedSel("User").Count()
	// 查询分页数据
	perPage := 10 // 每页显示数据
	// 当前页面
	page, err := this.GetInt("page")
	if err != nil {
		page = 1
	}
	pageCount := math.Ceil(float64(count) / float64(perPage)) // 页码向上取整
	if page < 1 {
		page = 1
	}
	if float64(page) > pageCount {
		page = int(pageCount)
	}
	// 上一页，下一页
	nextPage := float64(page + 1)
	beforePage := page - 1
	if nextPage > pageCount {
		nextPage = pageCount
	}
	if beforePage < 1 {
		beforePage = 1
	}
	offset := perPage * (page - 1)
	// _, err = o.QueryTable("Article").RelatedSel("User").Limit(perPage, offset).All(&articles)
	_, err = qs.RelatedSel("User").Limit(perPage, offset).All(&articles)
	if err != nil {
		logs.Info("查询失败", err)
		this.Ctx.WriteString(fmt.Sprintf("%v", err))
		return
	}
	// logs.Info(articles)
	// 查询所有标签并赋值
	var tags []models.Tag
	_, err = o.QueryTable("tag").All(&tags)
	if err != nil {
		logs.Info("查询失败", err)
		this.Ctx.WriteString(fmt.Sprintf("%v", err))
		return
	}

	this.Data["tagName"] = tagName
	this.Data["tags"] = tags
	this.Data["beforePage"] = beforePage
	this.Data["nextPage"] = nextPage
	this.Data["page"] = page
	this.Data["pageCount"] = pageCount
	this.Data["count"] = count
	this.Data["articles"] = articles
	this.Data["Admin"] = "admin"

	// Layout 设计
	this.Layout = "admin/layout.html"
	this.TplName = "index.html"
}

func (this *ArticleController) Detail() {
	// 1. 获取参数Id
	id, err := this.GetInt("id")
	if err != nil {
		this.Ctx.WriteString(fmt.Sprintf("%v", err))
		logs.Info("ArticleDetail err: ", err)
		return
	}
	// 2. 根据id查询数据
	o := orm.NewOrm()
	article := models.Article{
		Id: id,
	}
	err = o.Read(&article)
	if err != nil {
		this.Ctx.WriteString(fmt.Sprintf("%v", err))
		logs.Info("o.Read err: ", err)
		return
	}
	// 次数加一
	article.Amount++
	_, err = o.Update(&article)
	if err != nil {
		this.Ctx.WriteString(fmt.Sprintf("%v", err))
		logs.Info("o.Update err: ", err)
		return
	}
	this.Data["article"] = article

	// Layout 设计
	this.Layout = "admin/layout.html"
	this.TplName = "content.html"
}

func (this *ArticleController) ArticleAdd() {
	o := orm.NewOrm()
	// 查询所有标签并赋值
	var tags []models.Tag
	_, err := o.QueryTable("tag").All(&tags)
	if err != nil {
		logs.Info("查询失败", err)
		this.Ctx.WriteString(fmt.Sprintf("%v", err))
		return
	}

	this.Data["tags"] = tags

	// Layout 设计
	this.Layout = "admin/layout.html"
	this.TplName = "add.html"
}

func (this *ArticleController) HandleArticleAdd() {
	// 1. 接收参数
	title := strings.Trim(this.GetString("articleName"), "\t\r\n ")
	content := strings.Trim(this.GetString("content"), "\t\r\n ")
	tags := this.GetStrings("tags") // slice 切片标签ID

	fPath, err := Upload(this, "uploadname")
	// 创建ORM对象
	o := orm.NewOrm()
	article := models.Article{
		Title:   title,
		Content: content,
		ImgUrl:  fPath,
	}
	// 插入文章的关联用户
	userName := this.GetSession("username")
	if userName == nil {
		userName = "admin"
	}
	uName, ok := userName.(string) // 断言
	if !ok {
		uName = ""
	}
	User := models.User{Name: uName}
	err = o.Read(&User, "Name") // 指定查询字段
	if err != nil {
		this.Ctx.WriteString(fmt.Sprintf("%v", err))
		logs.Info("查询用户失败")
		return
	}
	// 一对多关联插入
	article.User = &User
	_, err = o.Insert(&article)
	if err != nil {
		this.Ctx.WriteString(fmt.Sprintf("%v", err))
		logs.Info("插入数据失败", err)
		return
	}
	// 多对多插入
	// 获取 article 多对多操作对象
	m2m := o.QueryM2M(&article, "tags") // 第一个参数对象必须有主键,第二个参数是字段名
	// 获取要插入的对象
	var tagModels []models.Tag
	_, err = o.QueryTable("tag").Filter("id__in", tags).All(&tagModels)
	if err != nil {
		this.Ctx.WriteString(fmt.Sprintf("%v", err))
		logs.Info("读取数据失败", err)
		return
	}
	// 多对多对象插入
	num, err := m2m.Add(tagModels) // 参数可以为对象，指针，对象数组，指针数组
	if err != nil {
		this.Ctx.WriteString(fmt.Sprintf("%v", err))
		logs.Info("多对多插入数据失败", err)
		return
	}
	logs.Info(num)
	this.Redirect("/article", 302)
}

func (this *ArticleController) Update() {
	// 1. 获取参数Id
	id, err := this.GetInt("id")
	if err != nil {
		this.Ctx.WriteString(fmt.Sprintf("%v", err))
		logs.Error("ArticleDetail err: ", err)
		return
	}
	// 2. 根据id查询数据
	o := orm.NewOrm()
	article := models.Article{
		Id: id,
	}
	err = o.Read(&article)
	if err != nil {
		this.Ctx.WriteString(fmt.Sprintf("%v", err))
		logs.Error("o.Read err: ", err)
		return
	}
	// 多对多查询
	_, err = o.LoadRelated(&article, "Tags")
	if err != nil {
		logs.Info("查询失败", err)
		this.Ctx.WriteString(fmt.Sprintf("%v", err))
		return
	}

	// 查询所有标签并赋值
	var tags []models.Tag
	_, err = o.QueryTable("tag").All(&tags)
	if err != nil {
		logs.Info("查询失败", err)
		this.Ctx.WriteString(fmt.Sprintf("%v", err))
		return
	}

	this.Data["tags"] = tags

	this.Data["article"] = article

	// Layout 设计
	this.Layout = "admin/layout.html"
	this.TplName = "update.html"
}

// 文章更新操作
func (this *ArticleController) HandleUpdate() {
	// 1. 获取参数Id
	id, err := this.GetInt("id")
	if err != nil {
		this.Ctx.WriteString(fmt.Sprintf("%v", err))
		logs.Error("ArticleDetail err: ", err)
		return
	}

	// 1. 接收参数
	title := strings.Trim(this.GetString("articleName"), "\t\r\n ")
	content := strings.Trim(this.GetString("content"), "\t\r\n ")
	tags := this.GetStrings("tags") // slice 切片标签ID

	// 校验文件上传
	fPath, err := Upload(this, "uploadname")
	if err != nil && err.Error() != "http: no such file" {
		this.Ctx.WriteString(fmt.Sprintf("%v", err))
		logs.Error("file upload err: ", err)
		return
	}

	// 2. 根据id查询数据
	o := orm.NewOrm()
	article := models.Article{
		Id: id,
	}
	err = o.Read(&article)
	if err != nil {
		this.Ctx.WriteString(fmt.Sprintf("%v", err))
		logs.Error("o.Read err: ", err)
		return
	}
	if fPath != "" {
		logs.Info(fPath)
		article.ImgUrl = fPath
	}
	article.Title = title
	article.Content = content
	_, err = o.Update(&article)
	if err != nil {
		this.Ctx.WriteString(fmt.Sprintf("%v", err))
		logs.Info("o.Update err: ", err)
		return
	}

	// 多对多修改--先删除关联表再新增
	logs.Info(id)
	_, err = o.QueryTable("article_tags").Filter("article_id", id).Delete()
	if err != nil {
		this.Ctx.WriteString(fmt.Sprintf("%v", err))
		logs.Info("多对多删除失败", err)
		return
	}
	// 获取 article 多对多操作对象
	m2m := o.QueryM2M(&article, "tags") // 第一个参数对象必须有主键,第二个参数是字段名
	// 获取要插入的对象
	var tagModels []models.Tag
	_, err = o.QueryTable("tag").Filter("id__in", tags).All(&tagModels)
	if err != nil {
		this.Ctx.WriteString(fmt.Sprintf("%v", err))
		logs.Info("读取数据失败", err)
		return
	}
	// 多对多对象插入
	num, err := m2m.Add(tagModels) // 参数可以为对象，指针，对象数组，指针数组
	if err != nil {
		this.Ctx.WriteString(fmt.Sprintf("%v", err))
		logs.Info("多对多插入数据失败", err)
		return
	}
	logs.Info(num)

	this.Redirect("/article", 302)
}

// 文章删除
func (this *ArticleController) Delete() {
	// 1. 获取参数Id
	id, err := this.GetInt("id")
	if err != nil {
		this.Ctx.WriteString(fmt.Sprintf("%v", err))
		logs.Info("ArticleDetail err: ", err)
		return
	}
	// 2. 根据id查询数据
	o := orm.NewOrm()
	article := models.Article{
		Id: id,
	}
	err = o.Read(&article)
	if err != nil {
		this.Ctx.WriteString(fmt.Sprintf("%v", err))
		logs.Info("o.Read err: ", err)
		return
	}
	// 删除
	_, err = o.Delete(&article)
	if err != nil {
		this.Ctx.WriteString(fmt.Sprintf("%v", err))
		logs.Info("o.Delete err: ", err)
		return
	}
	this.Redirect("/article", 302)
}

// Tag添加标签视图
func (this *ArticleController) TagAdd() {
	var tags []*models.Tag
	// 获取数据
	o := orm.NewOrm()
	_, err := o.QueryTable("tag").All(&tags)
	if err != nil {
		this.Ctx.WriteString(fmt.Sprintf("%v", err))
		logs.Info("查询失败", err)
		return
	}
	this.Data["tags"] = tags

	// Layout 设计
	this.Layout = "admin/layout.html"
	this.TplName = "addType.html"
}

// 添加Tag方法
func (this *ArticleController) HandleTagAdd() {
	// 1. 接收参数
	tagName := strings.Trim(this.GetString("tagName"), "\t\r\n ")
	// 创建ORM对象
	o := orm.NewOrm()
	tag := models.Tag{Name: tagName}
	_, err := o.Insert(&tag)
	if err != nil {
		this.Ctx.WriteString(fmt.Sprintf("%v", err))
		logs.Info("插入数据失败", err)
		return
	}
	this.Redirect("/article/tag", 302)
}

// Tag编辑标签视图
func (this *ArticleController) TagUpdate() {
	id, err := this.GetInt("id")
	if err != nil {
		this.Ctx.WriteString(fmt.Sprintf("%v", err))
		logs.Info("参数错误", err)
		return
	}
	var tag models.Tag
	// 获取数据
	o := orm.NewOrm()
	err = o.QueryTable("tag").Filter("id", id).One(&tag)
	if err != nil {
		this.Ctx.WriteString(fmt.Sprintf("%v", err))
		logs.Info("查询失败", err)
		return
	}
	this.Data["tag"] = tag

	// Layout 设计
	this.Layout = "admin/layout.html"
	this.TplName = "updateType.html"
}

// 修改Tag方法
func (this *ArticleController) HandleTagUpdate() {
	id, err := this.GetInt("id")
	if err != nil {
		this.Ctx.WriteString(fmt.Sprintf("%v", err))
		logs.Info("参数错误", err)
		return
	}
	// 1. 接收参数
	tagName := strings.Trim(this.GetString("tagName"), "\t\r\n ")
	// 创建ORM对象
	o := orm.NewOrm()
	tag := models.Tag{Id: id}
	err = o.Read(&tag)
	if err != nil {
		this.Ctx.WriteString(fmt.Sprintf("%v", err))
		logs.Info("查询数据失败", err)
		return
	}
	tag.Name = tagName
	_, err = o.Update(&tag)
	if err != nil {
		this.Ctx.WriteString(fmt.Sprintf("%v", err))
		logs.Info("o.Update err: ", err)
		return
	}
	this.Redirect("/article/tag", 302)
}

// Tag编辑标签视图
func (this *ArticleController) TagDelete() {
	id, err := this.GetInt("id")
	if err != nil {
		this.Ctx.WriteString(fmt.Sprintf("%v", err))
		logs.Info("参数错误", err)
		return
	}
	var tag models.Tag
	tag.Id = id
	// 获取数据
	o := orm.NewOrm()
	err = o.Read(&tag)
	if err != nil {
		this.Ctx.WriteString(fmt.Sprintf("%v", err))
		logs.Info("查询失败", err)
		return
	}
	_, err = o.Delete(&tag)
	if err != nil {
		this.Ctx.WriteString(fmt.Sprintf("%v", err))
		logs.Info("删除失败", err)
		return
	}
	this.Redirect("/article/tag", 302)
}

// 上传文件方法
func Upload(a *ArticleController, inputFileName string) (fPath string, err error) {
	// 1.1 校验文件上传
	f, h, err := a.GetFile(inputFileName) // 获取上传的文件
	if err != nil {
		logs.Info("get file err:", err)
		return
	}
	// a. 限制文件类型
	ext := path.Ext(h.Filename)
	// 验证后缀名是否符合要求
	var AllowExtMap = map[string]bool{
		".jpg":  true,
		".jpeg": true,
		".png":  true,
	}
	if _, ok := AllowExtMap[ext]; !ok {
		err = fmt.Errorf("file ext not allow %s", ext)
		return
	}
	// b. 校验文件大小
	if h.Size > 5*1024*1024 {
		err = fmt.Errorf("file size more 5M")
		return
	}
	// c. 创建目录 保存文件，设置文件名
	uploadDir := "static/upload/" + time.Now().Format("20060102/")
	err = os.MkdirAll(uploadDir, os.ModePerm)
	if err != nil {
		return
	}
	// 生成文件名称
	rand.Seed(time.Now().UnixNano())
	randNum := fmt.Sprintf("%d", rand.Intn(9999)+1000)
	hashName := md5.Sum([]byte(time.Now().Format("2006_01_02_15_04_05_") + randNum))
	fileName := fmt.Sprintf("%x", hashName) + ext
	// a.Ctx.WriteString(fileName)

	fPath = uploadDir + fileName
	defer f.Close() // 关闭上传的文件，不然的话会出现临时文件不能清除的情况
	err = a.SaveToFile(inputFileName, fPath)
	if err != nil {
		return
	}
	// a.Ctx.WriteString("上传成功~！！！！！！！")
	return "/" + fPath, nil
}
